﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Utils.Text;
using Utils.MathUtilitys;

namespace Utils.Drawing
{
    public class SpriteRenderer
    {
        /*
         * Fields
         */
        static Matrix matrixCenterOriToXNA;
        static SpriteRenderer defaultRenderer;

        SpriteBatch spriteBatch;
        PrimitiveHelper primitives;
        Viewport viewport;
        BasicEffect basicEffect;
        GraphicsDevice graphicsDevice;

        Matrix transformMatrix = Matrix.Identity;
        AbstractCoordinateTranslater coordinateTranslater;
        SpriteSortMode sortMode;

        BlendState blendState = BlendState.AlphaBlend;
        Effect currentEffect;

        /*
         * Properties
         */
        public static Matrix MatrixCenterOriToXNA { get { return matrixCenterOriToXNA; } }
        public static BlendState AdditivePremultiplied = AdditivePremultipliedBlendState();

        public PrimitiveHelper Primitives { get { return primitives; } }
        public Viewport Viewport { get { return viewport; } }
        public AbstractCoordinateTranslater CoordinateTranslater { get { return coordinateTranslater; } }
        public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }
        public BlendState BlendState { get { return blendState; } set { blendState = value; } }

        public static SpriteRenderer DEFAULT
        {
            get
            {
                if(defaultRenderer == null)
                {
                    defaultRenderer = new SpriteRenderer();
                }
                return defaultRenderer;
            }
        }

        public void Init(GraphicsDevice graphicsDevice)
        {
            this.spriteBatch = new SpriteBatch(graphicsDevice);
            this.primitives = new PrimitiveHelper(this);
            this.viewport = graphicsDevice.Viewport;
            this.graphicsDevice = graphicsDevice;
            CalculateMatricess();

            basicEffect = new BasicEffect(graphicsDevice);
            basicEffect.Projection = Matrix.CreateOrthographic
                (graphicsDevice.Viewport.Width,
                -graphicsDevice.Viewport.Height,
                0, 1);
        }

        public void UpdateViewport()
        {
            this.viewport = graphicsDevice.Viewport;
            CalculateMatricess();
        }

        static BlendState AdditivePremultipliedBlendState()
        {
            BlendState state = new BlendState();
            state.ColorSourceBlend = Blend.One;
            state.ColorDestinationBlend = Blend.One; 
            state.AlphaSourceBlend = Blend.SourceAlpha;
            state.AlphaDestinationBlend = Blend.One;                   
            return state;
        }

        void CalculateMatricess()
        {
            matrixCenterOriToXNA = Matrix.Identity;
            matrixCenterOriToXNA *= Matrix.CreateTranslation(viewport.Width / 2, viewport.Height / 2, 0);
        }

        public void Begin(SpriteSortMode sortMode)
        {
            this.sortMode = sortMode;
            spriteBatch.Begin(sortMode, blendState, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, currentEffect, transformMatrix);
        }

        public void Begin(SpriteSortMode sortMode, Matrix transformMatrix)
        {
            this.transformMatrix = transformMatrix;
            Begin(sortMode);
        }

        public void Begin(SpriteSortMode sortMode, Matrix transformMatrix, AbstractCoordinateTranslater coordinateTranslater)
        {
            this.coordinateTranslater = coordinateTranslater;

            Begin(sortMode, transformMatrix);
        }

        public void End()
        {
            spriteBatch.End();
            transformMatrix = Matrix.Identity;
            coordinateTranslater = null;
            currentEffect = null;
            blendState = BlendState.AlphaBlend;
        }

        public void ChangeSortMode(SpriteSortMode sortMode)
        {
            this.sortMode = sortMode;
            spriteBatch.End();
            Begin(sortMode);
        }

        public void Restart()
        {
            spriteBatch.End();
            Begin(sortMode);
        }

        public void Draw(
            Texture2D texture, Vector2 pos, Rectangle? source, Color color, 
            float rotation, Vector2 origin, Vector2 scale,
            BlendState blendState, Effect effect, SpriteEffects spriteEffects)
        {
            if (this.currentEffect != effect)
            {
                this.blendState = blendState;
                currentEffect = effect;
                Restart();
            }
            else
            {
                if (this.blendState != blendState)
                {
                    this.blendState = blendState;
                    graphicsDevice.BlendState = blendState;
                }
            }

            if (coordinateTranslater == null)
            {
                spriteBatch.Draw(texture, pos, null, color, rotation, origin, scale, spriteEffects, 0);
            }
            else
            {
                spriteBatch.Draw(
                    texture, 
                    coordinateTranslater.TranslatePosition(pos), 
                    source, 
                    color, 
                    coordinateTranslater.TranslateRotation(rotation), 
                    origin, 
                    scale, 
                    spriteEffects,
                    0);
            }
        }

        public void Draw(
            Texture2D texture, FRect rect, Rectangle? source, Color color,
            float rotation, Vector2 origin,
            BlendState blendState, Effect effect, SpriteEffects spriteEffects)
        {
            if (this.currentEffect != effect)
            {
                this.blendState = blendState;
                currentEffect = effect;
                Restart();
            }
            else
            {
                if (this.blendState != blendState)
                {
                    this.blendState = blendState;
                    graphicsDevice.BlendState = blendState;
                }
            }

            if (coordinateTranslater == null)
            {
                spriteBatch.Draw(texture, new Rectangle((int)Math.Round(rect.Left), (int)Math.Round(rect.Top), (int)Math.Round(rect.Width), (int)Math.Round(rect.Height)), null, color, rotation, origin, spriteEffects, 0);
            }
            else
            {
                Vector2 pos1 = new Vector2();
                pos1.X = rect.Left;
                pos1.Y = rect.Top;
                Vector2 pos2 = new Vector2();
                pos2.X = rect.Right;
                pos2.Y = rect.Bottom;

                pos1 = coordinateTranslater.TranslatePosition(pos1);
                pos2 = coordinateTranslater.TranslatePosition(pos2);

                Rectangle newRect = new Rectangle();
                newRect.X = (int)Math.Round(pos1.X);
                newRect.Y = (int)Math.Round(pos1.Y);
                newRect.Width = (int)Math.Round(pos2.X - pos1.X);
                newRect.Height = (int)Math.Round(pos2.Y - pos1.Y);

                spriteBatch.Draw(
                    texture,
                    newRect,
                    source,
                    color,
                    coordinateTranslater.TranslateRotation(rotation),
                    origin,
                    spriteEffects,
                    0);
            }
        }

        public void DrawText(
            LightString text, SpriteFont font, Vector2 pos, Vector2 origin,
            Vector2 scale, float rotation, Color col)
        {
            if (this.currentEffect != null)
            {
                currentEffect = null;
                Restart();
            }

            if (coordinateTranslater == null)
            {
                spriteBatch.DrawString(font, text.StringBuilder, pos, col, rotation,
                        origin, scale, SpriteEffects.None, 0);
            }
            else
            {
                spriteBatch.DrawString(font, text.StringBuilder, coordinateTranslater.TranslatePosition(pos),
                    col, rotation, origin, scale, SpriteEffects.None, 0);
            }
        }
    }
}
